package org.gwtEss.table;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.gwtEss.client.GwtEss;
import org.gwtEss.table.pages.DefaultPageNavigator;
import org.gwtEss.table.pages.NavigationWidget;

import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;


public abstract class OnDemandTable<VO> extends Composite implements HasRowCellHilite<VO>, HasRowCellClick<VO>{
	private Cols<VO> cols;
	private int rowsPerPage;
	private HeaderTableImpl header;
	private Tab<VO> layoutTab;
	private NavigationWidget nav;// = new DefaultPageNavigator();
	private int totalRows;
//	private int pxWidth, pxHeight;
	private int SCROLLBAR_WIDTH = 17; 
	int lastStartRow = -1; 
	Grid layoutVPanel;
//	VerticalPanel layoutVPanel;
	ScrollPanel layoutScrollPanel;
//	private int preFetchPages = 3;
	
	public OnDemandTable(Cols<VO> cols, /*int pxWidth, int pxHeight, */int rowsPerPage) {
		this.cols = cols; 
		this.rowsPerPage = rowsPerPage;
//		this.pxHeight = pxHeight;
//		this.pxWidth = pxWidth;
		layoutTab = createBodyTab();
		header = new HeaderTableImpl(cols/*, pxWidth*/);
		nav = new DefaultPageNavigator(){

			@Override
			public void onPageSelect(int startRow, int endRow, int pageNum) {
				reRender(startRow/*, sortCol, sortAsc*/);
//				tab.setWidth(pxWidth + "px");//TODO: This will make system very slow, optimize it!
			}
		};
		Widget layout = createLayout();
		
//		tab.setWidth(pxWidth + "px");//TODO: This will make system very slow, optimize it!
//		header.addCols(cols);
		initWidget(layout);
	}
	/*
	private Widget createLayout1(){
//		VerticalPanel vPanel = new VerticalPanel();
		layoutVPanel = new VerticalPanel();
		layoutVPanel.add(header);
		layoutVPanel.add(layoutTab);
		layoutVPanel.add((Widget)nav);
		return layoutVPanel;
	}*/
	
	@Override
	public void setPixelSize(int width, int height) {
		header.setPixelSize(width, header.getOffsetHeight());
		layoutTab.setPixelSize(width, height);
		layoutTab.resizeCols();
//		super.setPixelSize(width, height);
	}
	
	private Widget createLayout(){
//		VerticalPanel vPanel = new VerticalPanel();
//		layoutVPanel = new VerticalPanel();
		layoutVPanel = new Grid(3, 1);
//		layoutVPanel.setBorderWidth(1);
//		layoutVPanel.setWidth(pxWidth + "px");
//		vPanel.setWidth(pxWidth + SCROLLBAR_WIDTH + "px");
		
		
		layoutVPanel.setWidget(0, 0, header);
		layoutVPanel.setWidget(1, 0, layoutScrollPanel = new ScrollPanel(layoutTab){
			{
//				setHeight(pxHeight+"px");
//				setSize(pxWidth + SCROLLBAR_WIDTH + "px", pxHeight+"px");
			}
		});
		layoutVPanel.setWidget(2, 0, (Widget)nav);
		layoutVPanel.getCellFormatter().setHeight(1, 0, "100%");
		return layoutVPanel;
	}
	private Widget createLayout2(){
		VerticalPanel vPanel = new VerticalPanel();
		vPanel.add(header);
		vPanel.add((Widget)nav);
		return vPanel;
	}
	private Tab<VO> createBodyTab(){
		return new Tab<VO>(/*rowsPerPage, */cols){
//
//			@Override
//			public void onAfterRowCellHilite(ColWidget colWidget, VO vo) {
//				OnDemandTable.this.onAfterRowCellHilite(colWidget, vo);
//			}
//
//			@Override
//			public boolean onBeforeRowCellHilite(ColWidget colWidget, VO vo) {
//				return OnDemandTable.this.onBeforeRowCellHilite(colWidget, vo);
//			}
//
//			@Override
//			public void onAfterRowCellUnHilite(ColWidget colWidget, VO vo) {
//				OnDemandTable.this.onAfterRowCellHilite(colWidget, vo);
//			}
//
//			@Override
//			public void onBeforeRowCellUnHilite(ColWidget colWidget, VO vo) {
//				OnDemandTable.this.onAfterRowCellHilite(colWidget, vo);
//			}

			@Override
			public void onRowCellClick(VO data, ColWidget colWidget) {
				OnDemandTable.this.onRowCellClick(data, colWidget);
			};
			
			@Override
			public boolean isAlternateRowColor() {
				return OnDemandTable.this.isAlternateRowColor();
			}

			@Override
			public void onAfterCellHilite(VO data, ColWidget colWidget) {
				OnDemandTable.this.onAfterCellHilite(data, colWidget);
			}

			@Override
			public void onAfterCellUnHilite(VO data, ColWidget colWidget) {
				OnDemandTable.this.onAfterCellUnHilite(data, colWidget);
			}

			@Override
			public void onAfterRowHilite(VO data) {
				OnDemandTable.this.onAfterRowUnHilite(data);
			}

			@Override
			public void onAfterRowUnHilite(VO data) {
				OnDemandTable.this.onAfterRowUnHilite(data);
			}

			@Override
			public boolean onBeforeCellHilite(VO data, ColWidget colWidget) {
				return OnDemandTable.this.onBeforeCellHilite(data, colWidget);
			}

			@Override
			public boolean onBeforeCellUnHilite(VO data, ColWidget colWidget) {
				return OnDemandTable.this.onBeforeCellUnHilite(data, colWidget);
			}

			@Override
			public boolean onBeforeRowHilite(VO data) {
				return OnDemandTable.this.onBeforeRowHilite(data);
			}

			@Override
			public boolean onBeforeRowUnHilite(VO data) {
				return OnDemandTable.this.onBeforeRowUnHilite(data);
			}};
		

	}
//	private int[] colsWidth;
	
	private class HeaderTableImpl extends /*HeaderTableNew*/HeaderTable<VO>{

		public HeaderTableImpl(Cols<VO> cols/*, int pxWidth*/) {
			super(cols/*, pxWidth*/);
			// TODO Auto-generated constructor stub
		}
		@Override
		public void onColVisibilityChanged(Cols<VO> visibleCols) {
			for (int i=0; i< cols.size(); i++){
				layoutTab.setColVisibility(i, cols.get(i).isVisible());
				
			}
			layoutTab.resizeCols();
//			reRenderTab(visibleCols);
		}

		@Override
		public void onResizeBegin(ColWidget leftCol, ColWidget rightCol) {
			// TODO Auto-generated method stub
			
		}

		@Override
		public void onResizeEnd(ColWidget leftCol, ColWidget rightCol) {
			layoutTab.resizeCols();
		}

		@Override
		public void onSort(ColWidget col, Boolean asc) {
			sortCol = col;
			sortAsc = asc;
			reRender(currStartRow/*, sortCol, sortAsc*/);
		}

		@Override
		public void onFilterMenuSelect(ColWidget col,
				FetchCompleteCallBack<FilterMenuItem> filterListCallBack) {
			OnDemandTable.this.fetchColumnUniqueValues(col, filterListCallBack);
		}

		@Override
		public void onFilterMenuClose(ColWidget col,
				List<FilterMenuItem> filterCriterias) {
			if (filters == null)
				filters = new HashMap<ColWidget, List<FilterMenuItem>>();
			filters.put(col, filterCriterias);
			reRender(0/*, sortCol, sortAsc*/);
		}

		
	}
	private Map<ColWidget, List<FilterMenuItem>> filters;
	private ColWidget sortCol; 
	private Boolean sortAsc;
	public void render(int totalRows){
		this.totalRows = totalRows;
		nav.render(rowsPerPage, totalRows);
		reRender(0/*, sortCol, sortAsc*/);
	}
	private int currStartRow; 
	private void reRender(int startRow/*, ColWidget sortCol, Boolean asc*/){
//		int rowsUpto = startRow + numOfDisplayRows > totalRows?totalRows:startRow + numOfDisplayRows;
		int rowsUpto = startRow + rowsPerPage > totalRows?totalRows:startRow + rowsPerPage;
		this.currStartRow = startRow; 
//		int visibleStart = startRow; 
//		int visibleEnd = startRow + rowsPerPage;
//		if (lastStartRow != -1 &&(visibleStart > lastStartRow && visibleEnd < lastStartRow + rowsPerPage))
//			return;

		fetchRowData(startRow, rowsUpto, sortCol, sortAsc, filters, new FetchCompleteCallBackImpl(startRow, rowsUpto));
		lastStartRow = startRow;
	}
/*	private void reRender(int startRow){
//		int rowsUpto = startRow + numOfDisplayRows > totalRows?totalRows:startRow + numOfDisplayRows;
		int rowsUpto = startRow + rowsPerPage > totalRows?totalRows:startRow + rowsPerPage;
		int visibleStart = startRow; 
		int visibleEnd = startRow + rowsPerPage;
		if (lastStartRow != -1 &&(visibleStart > lastStartRow && visibleEnd < lastStartRow + rowsPerPage))
			return;
		ArrayList<Integer> fetchList = new ArrayList<Integer>();
		
		
		
		GwtEss.log.debug("tab: hidden");
		
		
		for (int row = startRow; row < rowsUpto; row++){
			VO vo = voCache.get(row);
			if (vo != null){
				layoutTab.render(row - startRow, vo);
			}else{
//				tab.renderBlank(row - startRow);//TODO: this is to be removed. 
				fetchList.add(row);//TODO: First check in the cache if this row is available
			}
		}
		if (fetchList.size() > 0){
			layoutTab.setVisible(false);
			fetchRowData(fetchList, new FetchCompleteCallBackImpl(fetchList, startRow));
		}
		lastStartRow = startRow;
	}*/
//	FastStringMap<VO> voCache = new FastStringMap<VO>();TODO
	Map<Integer, VO> voCache = new HashMap<Integer, VO>();
	private class FetchCompleteCallBackImpl implements FetchCompleteCallBack<VO>{

//		private ArrayList<Integer> requestedRows;
		private int startRow, endRow;

		public FetchCompleteCallBackImpl(/*ArrayList<Integer> fetchList, */int startRow, int endRow) {
//			this.requestedRows = fetchList;
			this.endRow = endRow;
			this.startRow = startRow;
		}
		public void onFetchComplete(List<VO> dataList/*, int newTotalRows*/) {
//			if (dataList==null || (dataList.size() != requestedRows.size())) 
//				throw new RuntimeException("Requested rows does not match with fetched rows");
//			renderTab(requestedRows, dataList, startRow);
			long start = System.currentTimeMillis();
//			OnDemandTable.this.totalRows = newTotalRows;
			assert dataList != null : "dataList(requested rows) should not be null";
			int rowsToRender = endRow-startRow;
			
			int dataListSize = dataList.size();
			rowsToRender = dataListSize < rowsToRender? dataListSize: rowsToRender; 
			
			assert !(startRow != 0 && rowsToRender != dataListSize)	: "Requested rows does not match with fetched rows. Expected " + dataList.size() + ", found " + rowsToRender;
			
			if (rowsToRender < rowsPerPage){
				layoutTab.removeRows(rowsToRender);
//				rowsToRender = dataListSize;
			}
			rowsToRender = dataListSize < rowsToRender? dataListSize: rowsToRender;

			for (int i=0; i< rowsToRender; i++){
				VO vo = dataList.get(i);
				int rowNum = i + startRow;//requestedRows.get(i);
				voCache.put(rowNum, vo);
				layoutTab.render(rowNum-startRow, vo);
			}
			GwtEss.log.custom("debug", "Time taken to render:" + (System.currentTimeMillis() - start));
//			tab.resizeCols(colsWidth);
			layoutTab.resizeCols();
			layoutTab.setVisible(true);
		}
	}

//	public abstract void fetchRowData(ArrayList<Integer> rowNumList, FetchCompleteCallBack<VO> fetchComplete);
	public abstract void fetchColumnUniqueValues(ColWidget col,
			FetchCompleteCallBack<FilterMenuItem> filterListCallBack);
	public abstract void fetchRowData(int startRow, int endRow, ColWidget sortCol,
			Boolean sortColAsc, Map<ColWidget, List<FilterMenuItem>> filters, final FetchCompleteCallBack<VO> fetchCompleteCallBack);
	public void setHeaderVisible(boolean visible) {
		header.setVisible(visible);
	}
	public int getRowsPerPage() {
		return rowsPerPage;
	}
	public void setRowsPerPage(int rowsPerPage) {
		this.rowsPerPage = rowsPerPage;
	}
	public void setNavigationWidgetVisible(boolean visible){
		((Widget)nav).setVisible(visible);
	}
//	//this method could be extended by the user if he needs the event
//	public void onBeforeRowHilite(ColWidget colWidget, VO vo) {};
//	//this method could be extended by the user if he needs the event
//	public void onAfterRowHilite(ColWidget colWidget, VO vo) {};
//	public void onBeforeRowUnHilite(ColWidget colWidget, VO vo) {};
//	public void onAfterRowUnHilite(ColWidget colWidget, VO vo) {};
}
